using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
using System.Runtime.InteropServices;
using Autofac;
using Castle.DynamicProxy.Generators;
using Microsoft.Practices.ServiceLocation;
using NQueries.Utils;
using Rhino.Mocks;

namespace NQueries.TestSupport
{
    public static class MockTools
    {
        static readonly Dictionary<Type, object> _SingletonMocks = new Dictionary<Type, object>();

        #region stubs

        public static T Stub<T>() { return (T)MockRepository.Stub(typeof(T)); }

        public static T StubInject<T>()
        {
            T stub = Stub<T>();
            Inject<T>(stub);
            return stub;
        }

        public static IDbSet<TEntity> StubRepository<TEntity>(IEnumerable<TEntity> entities) where TEntity : class, new()
        {
            IDbSet<TEntity> stub = StubInject<IDbSet<TEntity>>();
            SetupRepository(stub, entities);
            return stub;
        }

        #endregion

        #region stricts

        public static T Strict<T>(params object[] argumentsForConstructor) { return MockRepository.StrictMock<T>(argumentsForConstructor); }

        public static T StrictInject<T>()
        {
            T stub = Strict<T>();
            Inject<T>(stub);
            return stub;
        }

        public static IDbSet<TEntity> StrictRepository<TEntity>() where TEntity : class, new() { return CreateRepository(new TEntity[0]); }

        public static IDbSet<TEntity> StrictRepository<TEntity>(TEntity entity) where TEntity : class, new() { return CreateRepository(new[] { entity }); }

        public static IDbSet<TEntity> StrictRepository<TEntity>(TEntity[] entities) where TEntity : class, new() { return CreateRepository(entities); }

        public static IDbSet<TEntity> StrictRepository<TEntity>(ICollection<TEntity> entities) where TEntity : class, new() { return CreateRepository(entities); }

        public static IDbSet<TEntity> StrictRepository<TEntity>(IList<TEntity> entities) where TEntity : class, new() { return CreateRepository(entities); }

        public static IDbSet<TEntity> StrictRepository<TEntity>(List<TEntity> entities) where TEntity : class, new() { return CreateRepository(entities); }

        public static IDbSet<TEntity> StrictRepository<TEntity>(IEnumerable<TEntity> entities) where TEntity : class, new() { return CreateRepository(entities); }

        static IDbSet<TEntity> CreateRepository<TEntity>(IEnumerable<TEntity> entities) where TEntity : class, new()
        {
            IDbSet<TEntity> stub = Strict<IDbSet<TEntity>>();
            SetupRepository(stub, entities);
            return stub;
        }

        static void SetupRepository<TEntity>(IDbSet<TEntity> repository, IEnumerable<TEntity> entities) where TEntity : class, new()
        {
            IQueryable<TEntity> query = entities.AsQueryable();
            SetupResult.For(repository.ElementType).Return(query.ElementType);
            SetupResult.For(repository.Expression).Return(query.Expression);
            SetupResult.For(repository.GetEnumerator()).Return(query.GetEnumerator());
            SetupResult.For(repository.Provider).Return(query.Provider);

            IdSelector<TEntity> idSelector = delegate(object id)
            {
                ParameterExpression itemParameter = Expression.Parameter(typeof(TEntity), "item");

                Expression<Func<TEntity, bool>> whereExpression = Expression.Lambda<Func<TEntity, bool>>(
                    Expression.Equal(
                        Expression.Property(itemParameter, typeof(TEntity).GetPrimaryKeyPropertyInfo().Name),
                        Expression.Constant(id)
                        ),
                    new[] { itemParameter });

                return query.Where(whereExpression).Single();
            };
            SetupResult.For(repository.GetById(Arg<object>.Is.Anything)).IgnoreArguments().Do(idSelector);

            IDataContext dataContext = ServiceLocator.Current.GetInstance<IDataContext>();
            if (dataContext == null)
                throw new TestException("must setup IDataContext before setting up a repository");
            SetupResult.For(dataContext.Set<TEntity>()).Return(repository);
            SetupResult.For(dataContext.Set(typeof(TEntity))).Return(repository);
        }

        delegate TEntity IdSelector<TEntity>(object id);

        #endregion

        #region partials

        public static T Partial<T>() { return (T)MockRepository.PartialMock(typeof(T)); }

        #endregion

        static MockTools() { Reset(); }

        public static MockRepository MockRepository { get; private set; }

        public static void Reset(bool useStrictLogger, bool dontMockIncludeProvider)
        {
            _SingletonMocks.Clear();
            MockRepository = new MockRepository();

            ContainerBuilder containerBuilder = new ContainerBuilder();
            IContainer container = containerBuilder.Build();
            ServiceLocator.SetLocatorProvider(() => new AutofacServiceLocator(container));
        }

        public static void Reset() { Reset(false, false); }

        /// <summary>
        /// As we have problems to mock OMS Data, Info and other variables, we have to do this hack
        /// So, call this one each time you want to mock a OMS Repository variable.
        /// <seealso cref="http://stackoverflow.com/questions/3444581/mocking-com-interfaces-using-rhino-mocks"/>
        /// </summary>
        public static void AvoidReplicating()
        {
            if (!AttributesToAvoidReplicating.Contains(typeof(TypeIdentifierAttribute)))
                AttributesToAvoidReplicating.Add(typeof(TypeIdentifierAttribute));
        }

        public static void BackToRecordAll() { MockRepository.BackToRecordAll(); }

        public static IDisposable Record() { return MockRepository.Record(); }

        public static IDisposable Playback() { return MockRepository.Playback(); }

        public static IDataContext StrictDataContext()
        {
            IDataContext context = StrictInject<IDataContext>();
            Inject<IDataContext>(context);
            return context;
        }

        public static IDataContext StubDataContext()
        {
            IDataContext context = StubInject<IDataContext>();
            Inject<IDataContext>(context);
            return context;
        }

        public static void Inject<T>(object stub)
        {
            ServiceLocator.Current.InjectSingletonInstance(stub, typeof(T));
            ServiceLocator.Current.InjectSingletonInstance(stub, stub.GetType());
        }

        static void InjectSingletonInstance(this IServiceLocator serviceLocator, object instance, Type interfaceType)
        {
            ContainerBuilder builder = new ContainerBuilder();
            builder.RegisterInstance(instance).As(interfaceType).ExternallyOwned().SingleInstance();
            builder.Update(((AutofacServiceLocator)serviceLocator).Container);
        }
    }
}